﻿#region Copyright(C) 2018 FI Ltd. All rights reserved.
// ==================================================================================================
// Copyright(C) 2018 FI Ltd. All rights reserved.
//
// SYSTEM NAME	    : FI.Net Application Framework
// COMPONENT ID	    : FI.Core
// COMPONENT DESC   : FI.Net Core Foundation
//
// CREATED DATE/BY  : 18 Aug 2018 / Tian Mi
//
// REVISION HISTORY :	
// DATE/BY			SR#/CS/PM#/OTHERS		DESCRIPTION OF CHANGE
// ==================================================================================================
#endregion

using FI.Core.Properties;
using FI.Core.Utilities;
using System;
using System.Configuration;
using System.Linq;
using System.Xml;

namespace FI.Core.Configuration
{
    /// <summary>
    /// A <see cref="ConfigurationElementCollection"/> whose element type is <see cref="NameTypeConfigurationElement"/>.
    /// </summary>
    /// <typeparam name="T">The provider type.</typeparam>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1010:CollectionsShouldImplementGenericInterface")]
    public class NameTypeConfigurationElementCollection<T> : ConfigurationElementCollection where T : NameTypeConfigurationElement
    {
        #region Protected Methods
        /// <summary>
        /// When overridden in a derived class, creates a new <see cref="T:System.Configuration.ConfigurationElement" />.
        /// </summary>
        /// <returns>
        /// A new <see cref="T:System.Configuration.ConfigurationElement" />.
        /// </returns>
        protected override ConfigurationElement CreateNewElement()
        {
            return Activator.CreateInstance<T>();
        }

        /// <summary>
        /// Gets the element key for a specified configuration element when overridden in a derived class.
        /// </summary>
        /// <param name="element">The <see cref="T:System.Configuration.ConfigurationElement" /> to return the key for.</param>
        /// <returns>
        /// An <see cref="T:System.Object" /> that acts as the key for the specified <see cref="T:System.Configuration.ConfigurationElement" />.
        /// </returns>
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((NameTypeConfigurationElement)element).Name;
        }

        /// <summary>
        /// Gets the type of the configuration element.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <returns>The <see cref="ConfigurationElement"/> type.</returns>
        protected virtual Type GetConfigurationElementType(XmlReader reader)
        {
            Guard.ArgumentNotNull(reader, "reader");
            Type configurationElementType = null;
            if (reader.AttributeCount > 0)
            {
                for (bool go = reader.MoveToNextAttribute(); go; go = reader.MoveToNextAttribute())
                {
                    if (NameTypeConfigurationElement.TypeProperty == reader.Name)
                    {
                        string providerTypeName = reader.Value;
                        if (string.IsNullOrWhiteSpace(providerTypeName))
                        {
                            return null;
                        }
                        Type providerType = Type.GetType(providerTypeName, false);
                        if (null == providerType)
                        {
                            throw new ConfigurationErrorsException(Resources.ExceptionCannotResolveTypeName.Format((object)providerTypeName));
                        }
                        ConfigurationElementAttribute attribute = AttributeAccessor
                            .GetAttributes<ConfigurationElementAttribute>(providerType,false)
                            .OfType<ConfigurationElementAttribute>()
                            .FirstOrDefault();
                        if (null == attribute)
                        {
                            throw new InvalidOperationException(Resources.ExceptionNotApplyConfigurationElementAttribute.Format(providerType));
                        }
                        configurationElementType = attribute.ConfigurationElementType;
                    }
                }
                reader.MoveToElement();
            }
            return configurationElementType;
        }

        /// <summary>
        /// Causes the configuration system to throw an exception.
        /// </summary>
        /// <param name="elementName">The name of the unrecognized element.</param>
        /// <param name="reader">An input stream that reads XML from the configuration file.</param>
        /// <returns>
        /// true if the unrecognized element was deserialized successfully; otherwise, false. The default is false.
        /// </returns>
        protected override bool OnDeserializeUnrecognizedElement(string elementName, XmlReader reader)
        {
            if (base.AddElementName == elementName)
            {
                Type configurationElementType = this.GetConfigurationElementType(reader);
                T element = (T)Activator.CreateInstance(configurationElementType);
                element.Deserialize(reader);
                base.BaseAdd(element, true);
                return true;
            }
            return base.OnDeserializeUnrecognizedElement(elementName, reader);
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Gets the configuration element based on sepcified name.
        /// </summary>
        /// <param name="name">The name of configuration element.</param>
        /// <returns>The <see cref="ConfigurationElement"/></returns>
        public T GetConfigurationElement(string name)
        {
            Guard.ArgumentNotNullOrEmpty(name, "name");
            if (!base.BaseGetAllKeys().Contains(name))
            {
               throw new ConfigurationErrorsException(Resources.configurationElementNotExists.Format((object)name));
            }
            return base.BaseGet(name) as T;
        }
        #endregion
    }
}
